package com.dtp.extension.limiter.redis.ratelimiter;

import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import javax.annotation.Resource;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 基于Redis实现的抽象限流器
 *
 * @author yanhom
 * @since 1.0.8
 *
 * @remark muse
 **/
@SuppressWarnings("all")
public abstract class AbstractRedistRateLimiter implements RedisRateLimiter<List<Long>> {
    @Resource
    protected StringRedisTemplate stringRedisTemplate;
    private static final String SCRIPT_PATH = "/scripts/"; // lua脚本存放路径（sliding_window_rate_limiter.lua）
    protected static final String PREFIX = "dtp";
    private final RedisScript<List<Long>> script;
    protected static final AtomicInteger COUNTER = new AtomicInteger(0);

    public AbstractRedistRateLimiter(String scriptName) {
        DefaultRedisScript redisScript = new DefaultRedisScript<>();
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(SCRIPT_PATH + scriptName))); // 加载lua脚本
        redisScript.setResultType(List.class);
        this.script = redisScript;
    }

    @Override
    public List<String> getKeys(final String key) {
        String cacheKey = PREFIX + ":" + key; // "dtp:[key]"
        return Collections.singletonList(cacheKey);
    }

    /** 判断流量是否被允许，而不被限流 */
    @Override
    public List<Long> isAllowed(String key, long windowSize, int limit) {
        RedisScript<?> script = this.getScript();
        List<String> keys = this.getKeys(key);
        Object result = stringRedisTemplate.execute(script,
                                                    keys,
                                                    doubleToString(windowSize),
                                                    doubleToString(limit),
                                                    doubleToString(Instant.now().getEpochSecond()));
        // 如果result不为null，则封装为List返回；如果为null，则会抛出异常
        return Collections.unmodifiableList((List) Objects.requireNonNull(result));
    }

    /** 获得lua脚本 */
    @Override
    public RedisScript<List<Long>> getScript() {return script;}
    private String doubleToString(final double param) {return String.valueOf(param);}
}
